#include "mboard.h"
#include "mboard_private.h"
#include "scb_hw.h"
#include "nvmc_hw.h"
#include "nvic_hw.h"
#include "nvic.h"
#include "cpu.h"
#include "rcc.h"
#include "rcc_hw.h"
#include "pwr_hw.h"
#include "stk_hw.h"
#include "mdebug.h"
#include "umm_malloc.h"

/**
 * 板子上有一些外设的引脚和JTAG的引脚共用，因此调试时应选择SWD接口，需注意
 * STLINK连接SWD时，不要使用20PIN的排线，否则仍会干扰到JTAG的引脚，应使用
 * 杜邦线连接，同时需要将STLINK的1脚(TVCC)连接到板子上，该引脚从板子上取电，
 * 用于调试器上的电平转换芯片，不连接的话，调试器不能正常工作，该引脚不能
 * 为板子供电。F4芯片上，JTAG接口的引脚不需要重映射，只需修改复用或配置成
 * 普通输入输出即可用于其他功能。
 */

extern const uint32_t vector_table[98];
MBoardInfo_t mboardInfo;

static void mBoardSystemInit(void)
{
	/* CP10 CP11协处理器使能，和浮点运算有关 */
	SOC_SCB.CPACR = SCB_CPACR_FULL;
	/* 设置中断向量表 */
	SOC_SCB.VTOR = (uint32_t)vector_table;
	/* 使能Flash指令缓存、数据缓存、预取 */
	SOC_NVMC.ACR = NVMC_ACR_PRFTEN | NVMC_ACR_ICEN | NVMC_ACR_DCEN;
	/* 使能电源控制器时钟 */
	RCC_EnableApb1(RCC_CLK_APB1_PWR);
	/* 内部主调压器的输出电压级别1模式，最大工作频率168MHz，默认值为模式1，
	   最大频率168MHz */
	SOC_PWR.CR = PWR_CR_STOP | PWR_CR_PLS_2_3V | PWR_CR_VOS_1;
	/* 使能DMA时钟 */
	RCC_EnableAhb1(RCC_CLK_AHB1_DMA1 | RCC_CLK_AHB1_DMA2);
}

static void mBoardInterruptInit(void)
{
	uint32_t i;

	/* 清除所有中断使能 */
	for (i = 0; i < (sizeof(SOC_NVIC.ICER)/4); i++) {
		SOC_NVIC.ICER[i] = 0xFFFFFFFF;
	}

	/* PRIMASK=0，允许CPU响应所有异常 */
	CPU_EnableIrq();
	/* 设置中断分组，16个抢占优先级，0响应优先级 */
	NVIC_SetPriorityGroup(NVIC_GLOBAL_GROUP);
}

static void mBoardClockInit(void)
{
	uint32_t tmp1;

	/* 复位后使用HSI作为系统时钟，16M，各分频均为1，USB/SDIO/RNG时钟关闭 */
	mboardInfo.clk.ahb      = 16000000;
	mboardInfo.clk.apb1     = 16000000;
	mboardInfo.clk.apb2     = 16000000;
	mboardInfo.clk.apb1_tim = 16000000;
	mboardInfo.clk.apb2_tim = 16000000;

	/* 使能HSE时钟源 */
	tmp1 = SOC_RCC.CR;
	tmp1 |= RCC_CR_HSEON;
	SOC_RCC.CR = tmp1;

	/* PLL使用HSE时钟源，外部晶振8MHz
	   VCO = 8/PLLM*PLLN=336M，在192~432之间
	   PLLCLK = VCO/PLLP=168M，不大于168M
	   USB/SDIO/RNG = VCO/PLLQ=48M，不大于48M */
	SOC_RCC.PLLCFGR = RCC_PLLCFGR_PLLM_2_63D(4) |
	                  RCC_PLLCFGR_PLLN_50_432D(168) |
	                  RCC_PLLCFGR_PLLP_DIV2 | RCC_PLLCFGR_PLLSRC_HSE |
	                  RCC_PLLCFGR_PLLQ_2_15D(7);
	/* 使能PLL */
	tmp1 |= RCC_CR_PLLON;

	/* 等待外部时钟就绪 */
	while (!_RCC_IsHseReady(SOC_RCC));

	/* 使能PLL */
	SOC_RCC.CR = tmp1;
	while (!_RCC_IsPllReady(SOC_RCC));

	/* 使用PLL作为系统时钟SYSCLK
	   AHB = SYSCLK/HPRE=144M，不大于168M
	   APB1=AHB/PPRE1=36M，不大于42M
	   APB2=AHB/PPRE2=72M，不大于84M */
	SOC_RCC.CFGR = RCC_CFGR_SW_PLL | RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV4 |
	            RCC_CFGR_PPRE2_DIV2;

	/* 更新时钟参数，APBx分频不为1时，定时器时钟需要乘2 */
	mboardInfo.clk.ahb      = 168000000;
	mboardInfo.clk.apb1     = 42000000;
	mboardInfo.clk.apb2     = 84000000;
	mboardInfo.clk.apb1_tim = 84000000;
	mboardInfo.clk.apb2_tim = 168000000;
}

static void mBoardTimerInit(void)
{
	/* 设置滴答定时器，1ms=144M/8/1000=18000 */
	SOC_STK.LOAD = STK_LOAD_2_16777216D(mboardInfo.clk.ahb/8/1000);
	/* 加载计数值 */
	SOC_STK.VAL = 0;
	/* 设置优先级为最低，响应优先级无，滴答定时器不需要在NVIC中使能中断 */
	NVIC_SetPriority(NVIC_IRQ_STK, NVIC_PRIORITY_LOW, NVIC_PRIORITY_SUB_NONE);
	/* 定时器不分频，使能中断，使能计数 */
	SOC_STK.CTRL = STK_CTRL_EN | STK_CTRL_TICKINT | STK_CTRL_AHB_DIV8;
}

void mBoardInit(void)
{
	/* 系统全局配置 */
	mBoardSystemInit();
	/* 清除所有中断，使能CPU中断响应 */
	mBoardInterruptInit();
	/* 使用外部晶振变频到144MHz */
	mBoardClockInit();
	/* 初始化堆区域，使用SRAM2 16KB用作堆 */
	umm_init_heap((void *)SOC_SRAM2_START, SOC_SRAM2_SIZE);
	/* 使用滴答定时器产生1ms中断 */
	mBoardTimerInit();
	/* 初始化LED */
	mLedInit();
	/* 初始化TIM6，10ms单脉冲模式，用于按键消抖 */
	mTimBaseInit();
	/* 按键初始化 */
	mKeyInit();
	/* 初始化DMA */
	mDmaInit();
	/* 初始化USART */
	mUsartInit();
	/* 重定向打印接口 */
	//mDebugRedirect(mUsartTxBytes, mUsartTxString);
	/* 初始化SDIO */
	mSdioInit();
	/* 初始化SPI */
	mSpiInit();
	/* 初始化I2C */
	mI2cInit();
	/* 初始化FSMC */
	mFsmcInit();
}

void mBoardGoIdle(void)
{
	mUsartTxWaitFinish();
	mI2cWaitFinish();
}

void mBoardDelayMs(uint16_t ms)
{
	uint32_t tmp1 = mboardInfo.ms;
	uint32_t tmp2 = ms;

	tmp2 += tmp1;

	if (tmp2 < tmp1) {
		while (mboardInfo.ms > tmp1);
	}

	while (mboardInfo.ms < tmp2);
}

void mBoardDelayS(uint16_t s)
{
	uint32_t tmp1 = mboardInfo.ms;
	uint32_t tmp2 = s * 1000;

	tmp2 += tmp1;

	if (tmp2 < tmp1) {
		while (mboardInfo.ms > tmp1);
	}

	while (mboardInfo.ms <= tmp2);
}

void mBoardTest(void)
{
	while (1) {
		mDebugPuts("hello!!!");
		mBoardDelayS(1);
	}
}
